繼前天 用 FinMind 可以拿到股價資料,
昨天參考到的文章內也有教學如何使用爬蟲來抓取資料,
除了用FinMind,今天我們來研究一下如何用爬蟲抓股價好了~
這篇文章的作者08.爬股市每日價、量用下列這兩段網址分別可以抓到上市、上櫃公司的股價。
http://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date=20230808&stockNo=2330
有爬蟲經驗的朋友就知道,
response = json: 用json的格式回應資料。
date = 20230808: 日期設定在2023年8月8日(其實沒啥用),因為它會幫你整個月份抓出來。
stockNo = 2330:抓取股票代號為2330的這檔上市股票的資料 (2330=台積電)。
可以單純用個瀏覽器打開,看看資料大概會長什麼樣子(如下圖)
抓下來的資料裡面都有寫到欄位是甚麼
fields":["日期","成交股數","成交金額","開盤價","最高價","最低價","收盤價","漲跌價差","成交筆數"]
http://www.tpex.org.tw/web/stock/aftertrading/daily_trading_info/st43_result.php?d=112/08&stkno=6223
d=112/08: 日期設定在2023年8月,整個月份非常直接了當。
stkno = 6223: 抓取股票代號為6223的這檔上櫃股票的資料,(6223=旺矽)。
可以看見,跟上市的格式、內容不太一樣,但欄位資料的順序都一樣(如下圖)
上櫃這邊就沒寫,不過欄位一樣是
fields":["日期","成交股數","成交金額","開盤價","最高價","最低價","收盤價","漲跌價差","成交筆數"]
(我才不會說我看不懂他的東西 :D )
像是這邊把日期從民國年,+1911年轉成西元年,ex.「112/01/01」->「2023/01/01」。
還有把「成交股數」、「成交金額」和「成交筆數」這三個欄位的逗號刪去。
把「成交股數」和「成交金額」的數值除以1000。
最後把「成交股數」列的名稱改為「成交張數」。
import re
data['日期']=data['日期'].apply(lambda x: re.sub('(\d+)(/\d+/\d+)',lambda y: str(int(y.group(1))+1911)+y.group(2),x))
data[['成交股數','成交金額','成交筆數']]=data[['成交股數','成交金額','成交筆數']].applymap(lambda x:x.replace(',',''))
data.iloc[:,1:]=data.iloc[:,1:].applymap(float)
data[['成交股數','成交金額']]=data[['成交股數','成交金額']]/1000
data = data.rename(columns={'成交股數': '成交張數'})
還記得第三天的時候做的籌碼分析-實作簡單的籌碼分析,資料從哪裡收集? 有資料集還是要自已爬?
裡面的一些程式碼也會用到~
重點mplfinance
是 matplotlib 的一個擴展工具,專門用來繪製金融圖表
如果沒有安裝的話要記得pip install mplfinance
首先引入需要的套件庫
import pandas as pd
import requests
import re
import time
import mplfinance as mpf
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
from datetime import datetime
一樣把要畫的圖表字體改成中文的標楷體,除非用英文,不然不加這段會顯示空格
# 設定字體
zhfont = fm.FontProperties(fname=r'C:\Windows\Fonts\kaiu.ttf') # 使用原始字串
plt.rcParams['font.family'] = zhfont.get_name()
接下來定義一個根據上面下載"上市公司"資料的function
# 定義下載資料的函數
def fetch_data(date, stock_no):
url = f'http://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date={date}&stockNo={stock_no}'
max_retries = 5
for i in range(max_retries):
try:
response = requests.get(url)
response.raise_for_status() # 如果請求失敗,則引發異常
return response.json()
except requests.exceptions.RequestException as e:
print(f'Error fetching data for {date}: {e}. Retry {i + 1}/{max_retries}')
time.sleep(3)
raise Exception(f'Failed to fetch data for {date} after {max_retries} retries')
觀察過要下載的資料後,找方法和debug了好久,寫成這樣
這邊設定要抓的股票代號,這邊用台積電來舉例,stock_no='2330'
# 定義下載和合併資料的函數
def download_stock_data(start_year, start_month, end_year, end_month, stock_no='2330'):
data_frames = []
for year in range(start_year, end_year + 1):
start_m = start_month if year == start_year else 1
end_m = end_month if year == end_year else 12
for month in range(start_m, end_m + 1):
date_str = f'{year}{month:02d}01'
try:
json_data = fetch_data(date_str, stock_no)
columns = ['日期', '成交股數', '成交金額', '開盤價', '最高價', '最低價', '收盤價', '漲跌價差', '成交筆數']
df = pd.DataFrame(json_data['data'], columns=columns)
data_frames.append(df)
except Exception as e:
print(f'Error fetching data for {date_str}: {e}')
# 跳過未來的月份
current_date = datetime.now()
if year == current_date.year and month >= current_date.month:
break
all_data = pd.concat(data_frames, ignore_index=True)
return all_data
開始把日期轉換和資料前處理的部分寫出來
# 日期轉換和資料清洗
def clean_data(all_data):
all_data['日期'] = all_data['日期'].apply(lambda x: re.sub(r'(\d+)(/\d+/\d+)', lambda y: str(int(y.group(1)) + 1911) + y.group(2), x))
all_data[['成交股數', '成交金額', '成交筆數']] = all_data[['成交股數', '成交金額', '成交筆數']].replace(',', '', regex=True)
# 移除無法轉換為數字的異常值
def to_float(x):
try:
return float(x)
except ValueError:
return float('nan')
all_data.iloc[:, 1:] = all_data.iloc[:, 1:].applymap(to_float)
# 轉換成交股數和成交金額的單位
all_data[['成交股數', '成交金額']] = all_data[['成交股數', '成交金額']] / 1000
all_data = all_data.rename(columns={'成交股數': '成交張數'})
# 移除包含 NaN 的行
all_data = all_data.dropna()
# 轉換日期為日期格式
all_data['日期'] = pd.to_datetime(all_data['日期'])
# 重命名列以符合 mplfinance 的要求
all_data = all_data.rename(columns={
'開盤價': 'Open',
'最高價': 'High',
'最低價': 'Low',
'收盤價': 'Close',
'成交張數': 'Volume'
})
# 確保所有列都轉換為浮點數類型
all_data['Open'] = all_data['Open'].astype(float)
all_data['High'] = all_data['High'].astype(float)
all_data['Low'] = all_data['Low'].astype(float)
all_data['Close'] = all_data['Close'].astype(float)
all_data['Volume'] = all_data['Volume'].astype(float)
# 設置日期為索引
all_data.set_index('日期', inplace=True)
return all_data
這邊讓資料可以調整,因為有一些bug,把資料清理過一遍
設定開始時間今年4月~6月
# 主程序
start_year = 2024
start_month = 4
end_year = 2024
end_month = 6
all_data = download_stock_data(start_year, start_month, end_year, end_month)
cleaned_data = clean_data(all_data)
print(cleaned_data)
做好資料處理,把資料印出來看看!
all_data
Volume 成交金額 Open High Low Close 漲跌價差
日期
2024-04-01 22348.250 17301753.062 783.0 783.0 769.0 770.0 -9.0
2024-04-02 42219.075 33230356.267 784.0 790.0 783.0 790.0 20.0
2024-04-03 32909.892 25719094.412 783.0 785.0 778.0 780.0 -10.0
2024-04-08 40567.580 31925988.285 789.0 792.0 783.0 783.0 3.0
...
如果想要的話,可以把他保存起來變成xlsx 或 csv (改成 to_csv)
# 將結果保存到新的Excel檔案
results_df = pd.DataFrame(cleaned_data, columns=['日期', 'Volume', '成交金額', 'Open', 'High', 'Low', 'Close', '漲跌價差'])
results_df.to_excel('stock_2330_data.xlsx', index=False)
資料順利拿到,終於可以開始畫圖了!
# 繪製 K 線圖
def plot_data(all_data, title):
fig, ax = plt.subplots()
# mplfinance.plot 函數不接受 fontproperties 這個參數。
# 相反,我們可以在設置標題和標籤時手動指定字體屬性。
mpf.plot(all_data, type='candle', style='charles', ax=ax, warn_too_much_data=len(all_data) + 1)
ax.set_title(title, fontproperties=zhfont)
ax.set_ylabel('價格', fontproperties=zhfont)
plt.show()
# 這邊改一下圖表上的名稱為 台積電
plot_data(cleaned_data, f'台積電 {start_year}/{start_month} - {end_year}/{end_month} 每日股票交易價格和收盤情況')
但是畫出來有發現嗎...好像哪裡怪怪的? (如下圖)
創建一個自定義的顏色樣式字典,傳給mplfinance.plot函數
def plot_data(all_data, title):
fig, ax = plt.subplots()
# 自定義樣式
custom_style = mpf.make_mpf_style(
base_mpf_style='charles',
marketcolors={
'candle': {'up': 'red', 'down': 'green'},
'edge': {'up': 'red', 'down': 'green'},
'wick': {'up': 'red', 'down': 'green'},
'ohlc': {'up': 'red', 'down': 'green'},
'volume': {'up': 'red', 'down': 'green'},
'alpha': 1.0
}
)
# 繪圖
mpf.plot(all_data, type='candle', style=custom_style, ax=ax, warn_too_much_data=len(all_data) + 1)
ax.set_title(title, fontproperties=zhfont)
ax.set_ylabel('價格', fontproperties=zhfont)
plt.show()
就可以自己畫出美美的圖了,滿滿的成就感!
每日記錄:
禮拜日應該放假的說,
明天加碼來寫上櫃,還有合併一起判斷。
默默地完成了1/3的發文了 (覺得自己棒棒噠~
各位掰掰~